package com.thinkgem.jeesite.common.web;

import com.ckfinder.connector.FileUploadFilter;
import com.opensymphony.sitemesh.webapp.SiteMeshFilter;
import com.thinkgem.jeesite.common.servlet.FilesDownloadServlet;
import com.thinkgem.jeesite.common.servlet.ValidateCodeServlet;
import com.thinkgem.jeesite.modules.act.rest.servlet.JsonpCallbackFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Context;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.tomcat.JarScanType;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.scan.Constants;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContextEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Deque;
import java.util.Objects;

@Slf4j
@Configuration
public class WebConfig {

    private static final String PROJECT_NAME = "jeesite-boot";

    @Bean
    public ContextLoaderListener contextLoaderListener() {
        return new ContextLoaderListener() {
            @Override
            public void contextInitialized(ServletContextEvent event) {
                log.info("======================================================");
                log.info("Use webapp: {}", event.getServletContext().getRealPath(".")); // 打印一下docBase目录，就是webapp
                log.info("======================================================");
            }
        };
    }

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> jeeSiteWebServerFactoryCustomizer() {
        return new JeeSiteWebServerFactoryCustomizer();
    }

    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
    }

    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterFilterRegistrationBean() {
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean
                = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new DelegatingFilterProxy("shiroFilter"));
        filterRegistrationBean.addInitParameter("targetFilterLifecycle", "true");
        filterRegistrationBean.setOrder(10); // 该filter为ShiroFilter需要在其他Filter之前(除了encodingFilter)
        filterRegistrationBean.setUrlPatterns(Collections.singletonList("/*"));
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<SiteMeshFilter> siteMeshFilterFilterRegistrationBean(@Value("${adminPath}") String adminPath) {
        FilterRegistrationBean<SiteMeshFilter> filterRegistrationBean
                = new FilterRegistrationBean<>();
        SiteMeshFilter siteMeshFilter = new SiteMeshFilter(); // sitemesh 装饰过滤器，用于对jsp页面进行装饰实现公用分离
        filterRegistrationBean.setFilter(siteMeshFilter);
        filterRegistrationBean.setOrder(20);
        filterRegistrationBean.setUrlPatterns(Collections.singletonList(adminPath + "/*"));
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<JsonpCallbackFilter> jsonpCallbackFilterFilterRegistrationBean() {
        FilterRegistrationBean<JsonpCallbackFilter> filterRegistrationBean
                = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new JsonpCallbackFilter());
        filterRegistrationBean.setUrlPatterns(Collections.singletonList("/act/service/*"));
        filterRegistrationBean.setOrder(30);
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<FileUploadFilter> fileUploadFilterFilterRegistrationBean() {
        FilterRegistrationBean<FileUploadFilter> filterRegistrationBean
                = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new FileUploadFilter());
        filterRegistrationBean.addInitParameter("sessionCookieName", "JSESSIONID");
        filterRegistrationBean.addInitParameter("sessionParameterName", "jsessionid");
        filterRegistrationBean.setUrlPatterns(Collections.singletonList("/static/ckfinder/core/connector/java/connector.java"));
        filterRegistrationBean.setOrder(40);
        return filterRegistrationBean;
    }

    @Bean
    public ServletRegistrationBean<CKFinderConnectorServlet> ckFinderConnectorServletFilterRegistrationBean() {
        ServletRegistrationBean<CKFinderConnectorServlet> servletRegistrationBean
                = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(new CKFinderConnectorServlet());
        servletRegistrationBean.addInitParameter("XMLConfig", "/WEB-INF/ckfinder.xml");
        servletRegistrationBean.addInitParameter("debug", "false");
        servletRegistrationBean.addInitParameter("configuration", "com.thinkgem.jeesite.common.web.CKFinderConfig");
        servletRegistrationBean.addUrlMappings("/static/ckfinder/core/connector/java/connector.java");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Bean
    public ServletRegistrationBean<FilesDownloadServlet> userfilesDownloadServletServletRegistrationBean() {
        ServletRegistrationBean<FilesDownloadServlet> servletRegistrationBean
                = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(new FilesDownloadServlet());
        servletRegistrationBean.addUrlMappings("/userfiles/*");
        return servletRegistrationBean;
    }

    @Bean
    public ServletRegistrationBean<ValidateCodeServlet> validateCodeServletServletRegistrationBean() {
        ServletRegistrationBean<ValidateCodeServlet> servletRegistrationBean
                = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(new ValidateCodeServlet());
        servletRegistrationBean.addUrlMappings("/servlet/validateCodeServlet");
        return servletRegistrationBean;
    }

    @Bean
    public ServletRegistrationBean<DispatcherServlet> dispatcherServletServletRegistrationBean() {
        ServletRegistrationBean<DispatcherServlet> servletRegistrationBean
                = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(new DispatcherServlet());
        servletRegistrationBean.setLoadOnStartup(1);
        servletRegistrationBean.addInitParameter("contextConfigLocation", "classpath*:/spring-mvc*.xml");
        servletRegistrationBean.addUrlMappings("/");
        return servletRegistrationBean;
    }

    @Bean
    @ConditionalOnMissingBean(DefaultServlet.class)
    public ServletRegistrationBean<DefaultServlet> defaultServletServletRegistrationBean() {
        ServletRegistrationBean<DefaultServlet> servletRegistrationBean
                = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(new DefaultServlet());
        servletRegistrationBean.setLoadOnStartup(1);
        servletRegistrationBean.setName("defaultServletName"); // 处理spring-mvc.xml中配置的mvc:default-servlet-handler - defaultServletName处理静态资源
        servletRegistrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);
        servletRegistrationBean.addUrlMappings("/");
        return servletRegistrationBean;
    }

    @Bean
    public TomcatContextCustomizer tomcatContextCustomizer() {
        return new JeeSiteTomcatContextCustomizer();
    }

    static class JeeSiteTomcatContextCustomizer implements TomcatContextCustomizer {

        @Override
        public void customize(Context context) {
            context.setJarScanner(new IgnoreNotExistsJarScanner());
        }
    }

    @Slf4j
    static class IgnoreNotExistsJarScanner extends StandardJarScanner {
        @Override
        protected void process(JarScanType scanType, JarScannerCallback callback, URL url, String webappPath, boolean isWebapp, Deque<URL> classPathUrlsToProcess) throws IOException {

            File f = new File(url.getFile());
            // 跳过不存在的jar，其实JasperInitializer扫描tld并不是所有jar包都需要扫描解析，默认会扫描WEB-INF、classpath下的所有jar，具体看JasperInitializer实现
            // 可以通过配置环境变量Constants.SKIP_JARS_PROPERTY Constants.SKIP_JARS_PROPERTY来指定扫描哪些jar包，如果jar包比较多指定后可以提升一些tomcat启动速度
            if (url.getFile().endsWith(Constants.JAR_EXT) && !f.exists()) {
                log.warn("Skip not exists jar file: {}", url.getFile());
                return;
            }

            super.process(scanType, callback, url, webappPath, isWebapp, classPathUrlsToProcess);
        }
    }

    @Slf4j
    static class JeeSiteWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

        @Override
        public void customize(TomcatServletWebServerFactory factory) {
            // 重设docBase目录
            JeeSiteDocumentRoot documentRoot = new JeeSiteDocumentRoot();
            File doc = documentRoot.getValidDirectory();
            boolean oldDocExists = false;
            if (Objects.isNull(doc)) {
                log.info("Old doc-base dir can not detect!");
            } else {
                oldDocExists = doc.exists();
                log.info("Old doc-base dir: {}, exists: {}", doc.getAbsolutePath(), doc.exists());
            }
            if (!oldDocExists) {
                doc = findExternalDocBase();
                factory.setDocumentRoot(doc);
                log.info("New doc-base dir: {}, exists: {}", doc.getAbsolutePath(), doc.exists());
            }
        }

        private File findExternalDocBase() {
            File f = new File(".");
            String absPath = f.getAbsolutePath();
            if (absPath.contains(PROJECT_NAME)) {
                int idx0 = absPath.lastIndexOf(PROJECT_NAME);
                int idx1 = absPath.indexOf(File.separator, idx0 + 1);
                if (idx1 > 0) {
                    return new File(absPath.substring(0, idx1) + File.separator + "webapp");
                }
            }
            return f;
        }
    }
}
